package com.yyfax.common.tools;

import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Created by HenryZhang on 2017/3/15.<br>
 * Copyright © 2017 www.yylending.com. All Rights Reserved.<br><br>
 */
public class IMMLeaks {

	public static class ReferenceCleaner
			implements MessageQueue.IdleHandler, View.OnAttachStateChangeListener,
			ViewTreeObserver.OnGlobalFocusChangeListener {

		private final InputMethodManager inputMethodManager;
		private final Field mHField;
		private final Field mServedViewField;
		private final Method finishInputLockedMethod;

		public ReferenceCleaner(InputMethodManager inputMethodManager, Field mHField, Field mServedViewField,
				Method finishInputLockedMethod) {
			this.inputMethodManager = inputMethodManager;
			this.mHField = mHField;
			this.mServedViewField = mServedViewField;
			this.finishInputLockedMethod = finishInputLockedMethod;
		}

		@Override public void onGlobalFocusChanged(View oldFocus, View newFocus) {
			if (newFocus == null) {
				return;
			}
			if (oldFocus != null) {
				oldFocus.removeOnAttachStateChangeListener(this);
			}
			Looper.myQueue().removeIdleHandler(this);
			newFocus.addOnAttachStateChangeListener(this);
		}

		@Override public void onViewAttachedToWindow(View v) {
		}

		@Override public void onViewDetachedFromWindow(View v) {
			v.removeOnAttachStateChangeListener(this);
			Looper.myQueue().removeIdleHandler(this);
			Looper.myQueue().addIdleHandler(this);
		}

		@Override public boolean queueIdle() {
			clearInputMethodManagerLeak();
			return false;
		}

		private void clearInputMethodManagerLeak() {
			try {
				Object lock = mHField.get(inputMethodManager);
				// This is highly dependent on the InputMethodManager implementation.
				synchronized (lock) {
					View servedView = (View) mServedViewField.get(inputMethodManager);
					if (servedView != null) {

						boolean servedViewAttached = servedView.getWindowVisibility() != View.GONE;

						if (servedViewAttached) {
							// The view held by the IMM was replaced without a global focus change. Let's make
							// sure we get notified when that view detaches.

							// Avoid double registration.
							servedView.removeOnAttachStateChangeListener(this);
							servedView.addOnAttachStateChangeListener(this);
						} else {
							// servedView is not attached. InputMethodManager is being stupid!
							Activity activity = extractActivity(servedView.getContext());
							if (activity == null || activity.getWindow() == null) {
								// Unlikely case. Let's finish the input anyways.
								finishInputLockedMethod.invoke(inputMethodManager);
							} else {
								View decorView = activity.getWindow().peekDecorView();
								boolean windowAttached = decorView.getWindowVisibility() != View.GONE;
								if (!windowAttached) {
									finishInputLockedMethod.invoke(inputMethodManager);
								} else {
									decorView.requestFocusFromTouch();
								}
							}
						}
					}
				}
			} catch (Exception unexpected) {//IllegalAccessException | InvocationTargetException unexpected) {
				Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
			}
		}

		private Activity extractActivity(Context context) {
			while (true) {
				if (context instanceof Application) {
					return null;
				} else if (context instanceof Activity) {
					return (Activity) context;
				} else if (context instanceof ContextWrapper) {
					Context baseContext = ((ContextWrapper) context).getBaseContext();
					// Prevent Stack Overflow.
					if (baseContext == context) {
						return null;
					}
					context = baseContext;
				} else {
					return null;
				}
			}
		}
	}

//	/**
//	 * Fix for https://code.google.com/p/android/issues/detail?id=171190 .
//	 *
//	 * When a view that has focus gets detached, we wait for the main thread to be idle and then
//	 * check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got
//	 * focus, which is what happens if you press home and come back from recent apps. This replaces
//	 * the reference to the detached view with a reference to the decor view.
//	 *
//	 * Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.
//	 */
//	public static void fixFocusedViewLeak(Application application) {
//
//		// Don't know about other versions yet.
//		if (SDK_INT < KITKAT || SDK_INT > 22) {
//			return;
//		}
//
//		final InputMethodManager inputMethodManager =
//				(InputMethodManager) application.getSystemService(INPUT_METHOD_SERVICE);
//
//		final Field mServedViewField;
//		final Field mHField;
//		final Method finishInputLockedMethod;
//		final Method focusInMethod;
//		try {
//			mServedViewField = InputMethodManager.class.getDeclaredField("mServedView");
//			mServedViewField.setAccessible(true);
//			mHField = InputMethodManager.class.getDeclaredField("mServedView");
//			mHField.setAccessible(true);
//			finishInputLockedMethod = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
//			finishInputLockedMethod.setAccessible(true);
//			focusInMethod = InputMethodManager.class.getDeclaredMethod("focusIn", View.class);
//			focusInMethod.setAccessible(true);
//		} catch (NoSuchMethodException | NoSuchFieldException unexpected) {
//			Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
//			return;
//		}
//
//		application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
//			@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//				ReferenceCleaner cleaner =
//						new ReferenceCleaner(inputMethodManager, mHField, mServedViewField,
//								finishInputLockedMethod);
//				View rootView = activity.getWindow().getDecorView().getRootView();
//				ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
//				viewTreeObserver.addOnGlobalFocusChangeListener(cleaner);
//			}
//		});
//	}
}